[Ruby][AWS][GCM] Amazon SNS Mobile Push 用のサーバーを Heroku に構築する
はじめに
今回の記事は先日公開した記事の続編です。AWS SDK for Ruby を使って Amazon SNS に Publish して、Android 端末で Push 通知を受け取りたいと思います!
GCM (Google Cloud Messaging) に登録する
こちらでも簡単に説明していますが、同様に手順を確認しながら進めましょう。まずは Google Developers Console にアクセスし「Create Project」をクリックします。
適当な名前で作ります。
右のメニューから APIs & auth を選択し、「Google Cloud Messaging for Android」を探し出し、ON にします。
次に Credentials を選択し「CREATE NEW KEY」をクリックします。
どこで使うキーか聞かれるので「Server key」を選択します。
次にアクセスを許可する IP の設定が表示されますが、今回は特に使わないのでそのままで。
できあがったら API key をメモしておきます。あと URL にある ProjectID もあとで使うのでメモしておきます。これで GCM の準備完了!
Amazon SNS の App を作成する
次に Amazon SNS の出番です。基本的にこちらと同じように App を作成するだけです。まずは「Add a New App」をクリックします。
次に ApplicationName を適当な名前にし、Push Platform を GCM に変更、そして API Key にメモっておいた API Key を入力して「Add New App」をクリックします。
これでおしまいです。PlatformApplicationArn というものが生成されると思うので、メモしておいてください。また次項で Publish 用のサーバーを作りますが、AWS へのアクセスが必要になるのでついでに IAM User を作っておくと良いでしょう。
Publish 用のサーバーを Heroku に構築する
サーバーサイドの実装は以下の記事をベースにしているので、こちらをご覧いただいてから読んでいただければと思います。
[Ruby] Sinatra + PostgreSQL + Unicorn な Web サーバーを Heroku に構築する
まずは gem に aws-sdk を追加します。
vim Gemfile
Gemfile
... gem 'aws-sdk' ...
bundle install
次に AWS の設定ファイルを突っ込む aws.yml を作成します。
vim aws.yml
aws.yml
development: access_key_id: YOUR_ACCESS_KEY_ID secret_access_key: YOUR_SECRET_ACCESS_KEY region: YOUR_REGION test: access_key_id: YOUR_ACCESS_KEY_ID secret_access_key: YOUR_SECRET_ACCESS_KEY region: YOUR_REGION production: access_key_id: YOUR_ACCESS_KEY_ID secret_access_key: YOUR_SECRET_ACCESS_KEY region: YOUR_REGION
次に main.rb を編集します。AWS の設定を読み込むところと Publish 用の API を新たに用意しておきましょう。リクエストパラメータは message とし、好きなメッセージを送れるようにします。platform_application_arn は先ほど SNS の App 作成時にメモした PlatformApplicationArn を入れてください。
vim main.rb
main.rb
require 'sinatra' require 'sinatra/base' require 'active_record' require 'aws-sdk'
ActiveRecord::Base.configurations = YAML.load_file('database.yml') ActiveRecord::Base.establish_connection(ENV['RACK_ENV'])
# AWS の設定ファイルを読み込む AWS.config(YAML.load_file('aws.yml')[ENV['RACK_ENV']])
class User < ActiveRecord::Base end class MainApp < Sinatra::Base get '/' do User.all.to_json end post '/' do user = User.new user.registration_id = params[:registration_id] user.save! status 202 end # Publish API post '/publish' do sns = AWS::SNS.new client = sns.client # とりあえずUserの先頭にPublish response = client.create_platform_endpoint( platform_application_arn: 'YOUR_APP_ARN', token: User.first.registration_id ) endpoint_arn = response[:endpoint_arn] client.publish( target_arn: endpoint_arn, message: params[:message] ) end end [/ruby]
これでOKです。あとは Heroku にデプロイしてサーバーの準備完了です。
git add . git commit -m 'added amazon sns mobile push' git push heroku master
Subscribe する Android アプリを作成する
次に Subscribe する Android アプリを作成します。こちらと同じように実装します。Google Play Services のライブラリは最新をインストールしておいてください。まず適当なプロジェクトを作成し AndroidManifest.xml を編集します。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.samplesubscriber" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <permission android:name="com.example.samplesubscriber.permission.C2D_MESSAGE" android:protectionLevel="signature" /> <uses-permission android:name="com.example.samplesubscriber.permission.C2D_MESSAGE" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.samplesubscriber.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name=".GcmBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND" > <intent-filter> <action android:name="com.google.android.c2dm.intent.RECEIVE" /> <category android:name="com.example.samplesubscriber" /> </intent-filter> </receiver> <service android:name=".GcmIntentService" /> </application> </manifest>
次に IntentService と BroadcastReceiver を実装していきます。Subscribe したら GCMIntentService の onHandleIntent() が呼ばれるので、そこで Notification 通知を表示するように実装します。GCMBroadcastReceiver は WakefulBroadcastReceiver というクラスを継承していますが、このクラスは WakeLock 中に Service を起動することができる startWakefulService() という便利なメソッドがあるのでこれを使います。
GCMIntentService.java
package com.example.samplesubscriber; import android.app.IntentService; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.NotificationCompat; import android.util.Log; import com.google.android.gms.gcm.GoogleCloudMessaging; public class GcmIntentService extends IntentService { private static final String TAG = "GcmIntentService"; public GcmIntentService() { super("GcmIntentService"); } @Override protected void onHandleIntent(Intent intent) { Bundle extras = intent.getExtras(); GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this); String messageType = gcm.getMessageType(intent); if (!extras.isEmpty()) { if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) { Log.d(TAG, "messageType: " + messageType + ",body:" + extras.toString()); } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) { Log.d(TAG, "messageType: " + messageType + ",body:" + extras.toString()); } else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) { Log.d(TAG, "messageType: " + messageType + ",body:" + extras.toString()); // Notificationで通知 sendNotification(extras.getString("default")); } } GcmBroadcastReceiver.completeWakefulIntent(intent); } private void sendNotification(String message) { NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0); NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_launcher) .setContentTitle("GCM Notification") .setStyle(new NotificationCompat.BigTextStyle().bigText(message)) .setContentText(message); mBuilder.setContentIntent(contentIntent); manager.notify(0, mBuilder.build()); } }
GCMBroadcastReceiver.java
package com.example.samplesubscriber; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.support.v4.content.WakefulBroadcastReceiver; public class GcmBroadcastReceiver extends WakefulBroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { ComponentName comp = new ComponentName(context.getPackageName(), GcmIntentService.class.getName()); startWakefulService(context, (intent.setComponent(comp))); setResultCode(Activity.RESULT_OK); } }
ここまでできたら、あとは MainActivity で GCM に登録する処理を実装して終わりです。登録には GoogleCloudMessaging#register() を使います。引数には先ほどメモった GCM の ProjectID (SenderID) を渡し、登録が成功すると registrationId が渡されます。この registrationId を Heroku に構築するサーバーに対してリクエストして、データベースに登録します。
MainActivity.java
package com.example.samplesubscriber; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.client.ResponseHandler; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import android.app.Activity; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import com.google.android.gms.gcm.GoogleCloudMessaging; public class MainActivity extends Activity { /** Logcat出力用タグ. */ private static final String TAG = MainActivity.class.getSimpleName(); /** Google Cloud Messagingオブジェクト. */ private GoogleCloudMessaging mGcm; /** コンテキスト. */ private Context mContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mContext = getApplicationContext(); mGcm = GoogleCloudMessaging.getInstance(this); registerInBackground(); } private void registerInBackground() { new AsyncTask<Void, Void, String>() { @Override protected String doInBackground(Void... params) { String registrationId = ""; try { // GCM に登録して registrationID を受け取る if (mGcm == null) { mGcm = GoogleCloudMessaging.getInstance(mContext); } // 自分の ProjectID (SenderID) を入れる registrationId = mGcm.register("YOUR_SENDER_ID"); Log.d(TAG, "Device registered, registration ID=" + registrationId); // registrationID をサーバーに登録する registerId(registrationId); } catch (IOException ex) { Log.e(TAG, "Error :" + ex.getMessage()); } return registrationId; } @Override protected void onPostExecute(String msg) { } }.execute(null, null, null); } private void registerId(String registrationId) { Log.e(TAG, "POST処理開始"); // URL URI url = null; try { // 自分の Heroku サーバーの URL を入れる url = new URI("http://YOUR_APP_NAME.herokuapp.com/"); } catch (URISyntaxException e) { Log.e(TAG, e.toString()); } // POSTパラメータ付きでPOSTリクエストを構築 HttpPost request = new HttpPost(url); List<NameValuePair> post_params = new ArrayList<NameValuePair>(); post_params.add(new BasicNameValuePair("registration_id", registrationId)); try { // 送信パラメータのエンコードを指定 request.setEntity(new UrlEncodedFormEntity(post_params, "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } // POSTリクエストを実行 DefaultHttpClient httpClient = new DefaultHttpClient(); try { String ret = httpClient.execute(request, new ResponseHandler<String>() { @Override public String handleResponse(HttpResponse response) throws IOException { Log.d(TAG, "レスポンスコード:" + response.getStatusLine().getStatusCode()); // 正常に受信できた場合は200系 switch (response.getStatusLine().getStatusCode()) { case HttpStatus.SC_OK: case HttpStatus.SC_CREATED: case HttpStatus.SC_ACCEPTED: Log.d(TAG, "レスポンス取得に成功"); // レスポンスデータをエンコード済みの文字列として取得する return EntityUtils.toString(response.getEntity(), "UTF-8"); case HttpStatus.SC_NOT_FOUND: Log.e(TAG, "データが存在しない"); return null; default: Log.e(TAG, "通信エラー"); return null; } } }); Log.d(TAG, "Body:" + ret); } catch (IOException e) { Log.e(TAG, "通信に失敗:" + e.toString()); } finally { // shutdownすると通信できなくなる httpClient.getConnectionManager().shutdown(); } } }
これでクライアントアプリの完成です!
Push 通知を受け取ってみる!
それでは Push 通知を送ってみましょう。Heroku アプリの /publish にリクエストパラメータの message を適当につけて POST すると、クライアントアプリをインストールしている端末に Push 通知が届くはずです!
まとめ
ということで Push 通知を受け取るところまで実装してみました。新年の挨拶は年賀状からメールへと変わっていきましたが、その次は自分でサービス作って Push 通知を送る時代が…来る…(来ないw)
ということで2013年もお世話になりました。良いお年を!